﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using O1.IO.Storage;
using O1.Kernel.Data;

namespace O1.Kernel.IO.Storage
{
    internal abstract class StorageReader<T> : IEnumerator<T> where T : struct
    {
        /*
         * Can standardize the following:
         * Every record has a size
         * Every record has a version header
         * 
         * These list classes can be responsible for reading linkage and filtering versions
         * What if a chain is broken? Meaning, during a Tx, the linked list is updated using versions?
         * The integrity of the list must be maintained for a committed version in the event of an add or delete.
         * This list *could* also be responsible for cycle detection.
         * 
         * If the list is always ordered and traversed in order, cycle detection becomes easy and cheap...(did I regress?)
         * This is probably worth the overhead of ordering inserts (find last node and append).
         * Does this complicate deletions? Not really, start at the current index and link.
         * 
         * What to do when a cycle is detected? Skip back to watermark? Because this is a linked list,
         * there is no means to progress to other edges. Does this mean cycles are impossible? Yes in this context.
         * Given a vertex, a cycle can be had however. So, given a start vertex, the edge ID will determine if there 
         * is a cycle. Cycles are dependent on matching and filtering Status quo.
         * 
         * The behavior is that a reader transaction is set on moving.
         */

        private readonly BlockArray<byte[]> storage;

        private readonly TransactionToken transaction;

        private readonly long startIndex;

        private long currentIndex;

        private int currentOffset;

        public StorageReader(
            BlockArray<byte[]> storage,
            long startIndex,
            TransactionToken transaction)
        {
            Contract.Requires(storage != null);
            Contract.Requires(transaction != null);
            Contract.Requires(0 <= startIndex);
            Contract.Requires(startIndex < storage.Length);

            this.storage = storage;
            this.startIndex = startIndex;
            this.currentIndex = startIndex;
            this.currentOffset = -1;
            this.transaction = transaction;
        }

        public T Current
        {
            get
            {
                var buffer = this.storage[this.currentIndex];
                return this.ReadCurrent(buffer, this.currentOffset + CellLink.Size);
            }
        }

        object IEnumerator.Current
        {
            get
            {
                return this.Current;
            }
        }

        protected abstract int RecordSize { get; }

        public bool MoveNext()
        {
            this.currentOffset = 0;
            var buffer = this.storage[this.currentIndex];
            var header = CellLink.Read(buffer, 0);
            if (!this.transaction.IsReadVisible(header.EncodedTransaction))
            {
                var size = this.RecordSize;
                var offset = size;
                var visible = false;
                while (!(offset + size > buffer.Length))
                {
                    header = CellLink.Read(buffer, offset);
                    if (this.transaction.IsReadVisible(header.EncodedTransaction))
                    {
                        visible = true;
                        break;
                    }

                    offset += size;
                }

                // TODO: End of the road, nothing readable, chain broken.
                // Correctness here will rely heavily on proper DML handling...
                // Version clean ups will have to carfully lag behind transaction completion.
                // By putting latest changes first, the first visible value will be used.
                if (!visible)
                {
                    return false;
                }

                this.currentIndex = offset;
            }

            this.currentIndex = header.NextId;
            return header.NextId != 0;
        }

        public void Dispose()
        {
            GC.SuppressFinalize(this);
        }

        public void Reset()
        {
            this.currentIndex = this.startIndex;
            this.currentOffset = 0;
        }

        protected abstract T ReadCurrent(byte[] buffer, int offset);
    }
}
